package com.shr25.robot.protocol


import kotlinx.serialization.json.*
import moe.fuqiuluo.getResourceAsText
import moe.fuqiuluo.protocolInit
import net.mamoe.mirai.internal.utils.*
import net.mamoe.mirai.utils.*

import java.time.Instant
import java.time.OffsetDateTime
import java.time.ZoneId

@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
object FixProtocolVersion {

    private val clazz = MiraiProtocolInternal::class.java

    private val constructor = clazz.constructors.single()

    @PublishedApi
    internal val protocols: MutableMap<BotConfiguration.MiraiProtocol, MiraiProtocolInternal> by lazy {
        try {
            MiraiProtocolInternal.protocols
        } catch (_: NoSuchMethodError) {
            with(MiraiProtocolInternal) {
                this::class.members
                    .first { "protocols" == it.name }
                    .call(this)
            }.cast()
        }
    }

    @PublishedApi
    internal fun <T> MiraiProtocolInternal.field(name: String, default: T): T {
        @Suppress("UNCHECKED_CAST")
        return kotlin.runCatching {
            val field = clazz.getDeclaredField(name)
            field.isAccessible = true
            field.get(this) as T
        }.getOrElse {
            default
        }
    }

    @PublishedApi
    internal fun MiraiProtocolInternal.change(block: MiraiProtocolInternalBuilder.() -> Unit): MiraiProtocolInternal {
        val builder = MiraiProtocolInternalBuilder(this).apply(block)
        return when (constructor.parameterCount) {
            10 -> constructor.newInstance(
                builder.apkId,
                builder.id,
                builder.ver,
                builder.sdkVer,
                builder.miscBitMap,
                builder.subSigMap,
                builder.mainSigMap,
                builder.sign,
                builder.buildTime,
                builder.ssoVersion
            )
            11 -> constructor.newInstance(
                builder.apkId,
                builder.id,
                builder.ver,
                builder.sdkVer,
                builder.miscBitMap,
                builder.subSigMap,
                builder.mainSigMap,
                builder.sign,
                builder.buildTime,
                builder.ssoVersion,
                builder.supportsQRLogin
            )
            else -> this
        } as MiraiProtocolInternal
    }

    @PublishedApi
    internal class MiraiProtocolInternalBuilder(impl: MiraiProtocolInternal) {
        var apkId: String = impl.field("apkId", "")
        var id: Long = impl.field("id", 0)
        var ver: String = impl.field("ver", "")
        var sdkVer: String = impl.field("sdkVer", "")
        var miscBitMap: Int = impl.field("miscBitMap", 0)
        var subSigMap: Int = impl.field("subSigMap", 0)
        var mainSigMap: Int = impl.field("mainSigMap", 0)
        var sign: String = impl.field("sign", "")
        var buildTime: Long = impl.field("buildTime", 0)
        var ssoVersion: Int = impl.field("ssoVersion", 0)
        var supportsQRLogin: Boolean = impl.field("supportsQRLogin", false)
    }

    /**
     * 从本地加载android_phone协议
     *
     * @since 1.8.0
     */
    @JvmStatic
    fun loadAndroid(version: String) {
        protocolInit(version)
        loadAndroidPhone(version)
        loadAndroidPad(version)
    }

    /**
     * 从本地加载android_phone协议
     *
     * @since 1.8.0
     */
    @JvmStatic
    fun loadAndroidPhone(version: String) {
        val json: JsonObject = Json.parseToJsonElement(getResourceAsText("/txlib/" + version + "/android_phone.json")).jsonObject
        store(BotConfiguration.MiraiProtocol.ANDROID_PHONE, json)
    }
    /**
     * 从本地加载android_pad协议
     *
     * @since 1.8.0
     */
    @JvmStatic
    fun loadAndroidPad(version: String) {
        val json: JsonObject = Json.parseToJsonElement(getResourceAsText("/txlib/" + version + "/android_pad.json")).jsonObject
        store(BotConfiguration.MiraiProtocol.ANDROID_PAD, json)
    }

    @JvmStatic
    private fun store(protocol: BotConfiguration.MiraiProtocol, json: JsonObject) {
        check(json.getValue("app_id").jsonPrimitive.long != 0L) { "载入的 ${protocol.name.lowercase()}.json 有误" }
        protocols.compute(protocol) { _, impl ->
            when {
                null == impl -> null
                impl.runCatching { id }.isFailure -> impl.change {
                    apkId = json.getValue("apk_id").jsonPrimitive.content
                    id = json.getValue("app_id").jsonPrimitive.long
                    ver = json.getValue("sort_version_name").jsonPrimitive.content
                    sdkVer = json.getValue("sdk_version").jsonPrimitive.content
                    miscBitMap = json.getValue("misc_bitmap").jsonPrimitive.int
                    subSigMap = json.getValue("sub_sig_map").jsonPrimitive.int
                    mainSigMap = json.getValue("main_sig_map").jsonPrimitive.int
                    sign = json.getValue("apk_sign").jsonPrimitive.content.hexToBytes().toUHexString(" ")
                    buildTime = json.getValue("build_time").jsonPrimitive.long
                    ssoVersion = json.getValue("sso_version").jsonPrimitive.int
                }
                else -> impl.apply {
                    apkId = json.getValue("apk_id").jsonPrimitive.content
                    id = json.getValue("app_id").jsonPrimitive.long
                    buildVer = json.getValue("sort_version_name").jsonPrimitive.content
                    ver = buildVer.substringBeforeLast(".")
                    sdkVer = json.getValue("sdk_version").jsonPrimitive.content
                    miscBitMap = json.getValue("misc_bitmap").jsonPrimitive.int
                    subSigMap = json.getValue("sub_sig_map").jsonPrimitive.int
                    mainSigMap = json.getValue("main_sig_map").jsonPrimitive.int
                    sign = json.getValue("apk_sign").jsonPrimitive.content.hexToBytes().toUHexString(" ")
                    buildTime = json.getValue("build_time").jsonPrimitive.long
                    ssoVersion = json.getValue("sso_version").jsonPrimitive.int
                    appKey = json.getValue("app_key").jsonPrimitive.content
                }
            }
        }
    }

    /**
     * 协议版本信息
     *
     * @since 1.6.0
     */
    @JvmStatic
    fun info(): Map<BotConfiguration.MiraiProtocol, String> {
        return protocols.mapValues { (protocol, info) ->
            val version = info.field("buildVer", null as String?) ?: info.field("ver", "???")
            val epochSecond = info.field("buildTime", 0L)
            val datetime = OffsetDateTime.ofInstant(Instant.ofEpochSecond(epochSecond), ZoneId.systemDefault())

            "%-13s  %-12s  %s".format(protocol, version, datetime)
        }
    }
}